﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;
using System.Xml.Linq;

using Perseus;
using Perseus.Data;
using Perseus.Plugins;
using Perseus.Win32;
using Perseus.Windows;

using Hetymine.Executioner.Commands;
using Hetymine.Executioner.Data;
using Hetymine.Executioner.Data.Console;
using Hetymine.Executioner.Plugins;
using Hetymine.Executioner.Triggers;
/**
 * TODO:
 * - ICommandIcon interface so plugins can implement it and have a generic way of getting an
 * icon assoicated with a command
 */
namespace Hetymine.Executioner {
    public partial class Executioner {
        #region Private Fields
        private static Executioner _Instance;
        private static string _Version;
        private static string _VersionString;

        private string _Platform;
        private bool _IsLoaded; // If executioner commands, config, history, etc. has been loaded
        private bool _IsPluginsLoaded; // Set to true once all plugins have been loaded
        
        // Keep track of which plugins were loaded so after they are 
        // disabled in config, they can still be unloaded
        private List<string> _LoadedPlugins;

        // Interop helper for hooking into messages
        private static WindowInteropHelper _Handle;
        private HwndSource _HwndSource;

        // Commands
        internal static int _ExecutionCount; // To prevent self executing commands from getting stuck in an infinite loop
        private bool _IsFailure; // Has a problem occured while executing a command
        private bool _IsError; // Has a an error occured at no fault of the user while executing a command
        private bool _IsExit; // The exit command was called
        private FullScreenDetector _FullScreenDetector;
        private List<CommandInfo> _Queue;

        // Executioner windows
        private Config _ConfigWindow;
        #endregion
        #region Initialization
        public static void Initialize() {
            if (Executioner._Instance == null) {
                Executioner._Instance = new Executioner();
                Executioner._Instance.Initialized();

                // Unload executioner before windows shuts down
                App.Current.SessionEnding += (o, e) => {
                    if (Executioner._Instance._IsLoaded) {
                        Executioner._Instance.Unload();
                    }
                };
            }
        }
        private void Initialized() {
            if (Environment.OSVersion.Platform == PlatformID.Unix ||
                Environment.OSVersion.Platform == PlatformID.MacOSX // loooool
            ) {
                this._Platform = "linux";
            }
            else {
                this._Platform = "windows";
            }
            this._IsLoaded = false;
            this._IsPluginsLoaded = false;
            this._IsExit = false;
            
            // Set version info
            var assemblyInfo = new AssemblyInfo(Executioner._Instance.GetType().Assembly);
            Executioner.Version = assemblyInfo.Version;
            Executioner.VersionString = assemblyInfo.Title + " v" + assemblyInfo.Version;

            // Handle
            Executioner._Handle = new WindowInteropHelper(App.Current.MainWindow);
            this._HwndSource = HwndSource.FromHwnd(Executioner._Handle.Handle);
            this._HwndSource.AddHook(new HwndSourceHook(this.HookHandler));

            // Event to dynamically load assemblies used by plugins to allow them to
            // reside in the plugin directory instead of the main directory
            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
            Executioner.PluginService = new PluginService<IPlugin>();
            Executioner.PluginService.AssemblyLoading += new PluginEventHandler(PluginService_AssemblyLoading);
            Executioner.PluginService.LoadPlugins();

            Executioner._ExecutionCount = 0;
            this._IsFailure = false;
            this._IsError = false;

            // List of built in executioner commands
            this.ExecutionerCallStrings = new List<string>();
            this.ExecutionerCallStrings.AddRange(new string[] { 
                "-clear", "-cmds", "-config", "-display", "-exec", "-exit", "-reload", "-scripts", "-website"
            });
            
            // Load dynamic data such as configuration and commands
            this.Load();

            // Execute command line
            List<string> args = new List<string>(Environment.GetCommandLineArgs());
            if (args.Count > 1) {
                args.RemoveAt(0);
                this.ExecuteCallstringCommands(string.Join(" ", args.ToArray()));
            }
        }
        ~Executioner() {
            // This shouldn't be loaded if properly exited, if it is, some unloaders might fail do
            // to not having access to the windows handle
            if (this._IsLoaded) {                
                this.Unload();
            }
        }
        private void Exit() {
            if (this._IsLoaded) {
                this.Unload();
            }

            App.Current.Shutdown();
        }
        
        private void Load() {
            try {
                // Load configuration from config.ini
                string fileName = Executioner.Directory + "config.ini";
                if (File.Exists(fileName)) {
                    string config = Executioner.Data.Load(fileName);
                    Executioner.Config = IniFile.FromString(config);
                }
                else {
                    Executioner.Config = IniFile.FromResource(
                        new Uri("pack://application:,,,/Resources/config.ini")
                    );
                }
                Executioner.Config.FileName = fileName;
            }
            catch {
                App.Log("Error loading config.ini resource.");
                Executioner.Sounds.Error();
                Executioner.Config = new IniFile();
            }

            this._LoadedPlugins = new List<string>();

            // In cases where plugins need to interact with other plguins, after they have loaded.
            foreach (PluginInstance<IPlugin> p in Executioner.PluginService.Plugins) {
                bool enabled = p.Instance.IsEnabled;
                if (p.Instance is ILoader) {
                    try {
                        if (enabled) {                            
                            ((ILoader)p.Instance).Load();
                        }
                    }
                    catch {
                        enabled = false;
                        App.Log("Error loading " + p.Name + " plugin.");
                        Executioner.Sounds.Error();
                    }
                }
                if (enabled) {
                    this._LoadedPlugins.Add(p.Name);
                }
            }

            this._IsPluginsLoaded = true;
            
            if (Executioner.Loading != null) {
                Executioner.Loading(this, EventArgs.Empty);
            }

            // Setup full screen detection
            this.InitializeFullScreenDetection();

            // Load commands from commands.xml            
            this.LoadCommands();

            // Load history from history.txt
            Executioner.History = new HistoryManager();

            this._IsLoaded = true;
            if (Executioner.Loaded != null) {
                Executioner.Loaded(this, EventArgs.Empty);
            }
        }
        private void Unload() {
            if (Executioner.Unloading != null) {
                Executioner.Unloading(this, EventArgs.Empty);
            }

            this._IsPluginsLoaded = false;
                    
            // Check for plugins with custom unload method
            foreach (PluginInstance<IPlugin> p in Executioner.PluginService.Plugins) {
                if (p.Instance is ILoader) {
                    try {
                        if (this._LoadedPlugins.Contains(p.Name)) {
                            ((ILoader)p.Instance).Unload();
                        }
                    }
                    catch {
                        App.Log("Error unloading " + p.Name + " plugin.");
                        Executioner.Sounds.Error();
                    }
                }
            }

            Executioner.History.Save();

            using (var sw = new StringWriter()) {                
                Executioner.Config.Save(sw);
                Executioner.Data.Save(Executioner.Config.FileName, sw.ToString());
            }            
            Executioner.Config = null;

            this._IsLoaded = false;

            if (Executioner.Unloaded != null) {
                Executioner.Unloaded(this, EventArgs.Empty);
            }
        }
        private void Reload() {
            this.Unload();
            this.Load();
        }
        #endregion
        #region Events
        public static event EventHandler Loading; // Called during the loading process, config is loaded at this point
        public static event EventHandler Loaded; // Called when everything has loaded
        public static event EventHandler Unloading; // Called during the unloading process
        public static event EventHandler Unloaded; // Called when everything has unloaded
        public static event EventHandler CommandsLoaded; // Called after commands are loaded
        #endregion
        #region Plugins
        /// <summary>
        /// Dynamically resolves required assemblies used by plugins.
        /// </summary>
        /// <returns>The loaded assembly.</returns>
        private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) {
            string assemblyName = args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";

            string path = Executioner.Directory + Path.DirectorySeparatorChar + "plugins";
            string[] dirs = System.IO.Directory.GetDirectories(path);

            string fileName = path + Path.DirectorySeparatorChar + assemblyName;

            if (File.Exists(fileName)) {
                return Assembly.LoadFrom(fileName);
            }

            foreach (string d in dirs) {
                string[] files = System.IO.Directory.GetFiles(d);
                foreach (string f in files) {
                    fileName = Path.GetFileName(f);
                    if (fileName == assemblyName) {
                        return Assembly.LoadFrom(f);
                    }
                }
            }

            return null;
        }
        public static PluginService<IPlugin> PluginService { get; protected set; }
        public static bool IsPluginLoaded(string plugin) {
            if (Executioner._Instance._IsPluginsLoaded) {
                var p = Executioner.PluginService[plugin];
                if (p != null && Executioner._Instance._LoadedPlugins.Contains(p.Name)) {
                    return true;
                }
            }

            return false;
        }
        private void PluginService_AssemblyLoading(object sender, PluginEventArgs e) {
            string name = e.Name.ToLower();

            // Do not load plugin if it is for a different platform
            if (name.Contains("_") && !name.EndsWith("_" + this._Platform, StringComparison.Ordinal)
            ) {
                e.Cancel = true;
            }            
        }
        #endregion
        #region Full Screen Detection
        private void InitializeFullScreenDetection() {
            int interval = Executioner.Config.GetInt("Executioner", "Full Screen Detection Interval", 1000);
            interval = Math.Max(1, interval);
            this._FullScreenDetector = new FullScreenDetector(interval);
            this._FullScreenDetector.FullScreenExit += new EventHandler(FullScreenDetector_FullScreenExit);
            this._Queue = new List<CommandInfo>();
        }
        private void FullScreenDetector_FullScreenExit(object sender, EventArgs e) {
            // When application exists full screen, execute all queued commands
            foreach (CommandInfo cmd in this._Queue) {
                Executioner.ExecuteCommand(cmd.Command, cmd.Arguments, cmd.KeepHistory);
            }
            // Reset queue and detector
            this._Queue = new List<CommandInfo>();
            this._FullScreenDetector.IsEnabled = false;
        }
        #endregion
        #region Commands
        public static Dictionary<string, List<ICommand>> Commands { get; protected set; }
        private Dictionary<string, List<ICommand>> CloneCommands() {
            Dictionary<string, List<ICommand>> cmds = new Dictionary<string, List<ICommand>>();

            foreach (string type in Executioner.Commands.Keys) {
                cmds[type] = new List<ICommand>();
                foreach (ICommand c in Executioner.Commands[type]) {
                    cmds[type].Add(c.Clone() as ICommand);
                }
            }

            return cmds;
        }

        private List<string> ExecutionerCallStrings { get; set; }
        public static List<string> CallStrings {
            get {
                List<string> callStrings = new List<string>();

                var commands = from cg in Executioner.Commands.Values
                               from c in cg
                               where !(c is UnknownCommand)
                               select c;

                foreach (IExecutable c in commands) {
                    if (c.CallStrings != null) {
                        // Special case for file extension targeting
                        // applys to trigger commmands only (*.jpg;*.png)
                        if (c is TriggerCommand) {
                            foreach (string cs in c.CallStrings) {
                                if (!Regex.Match(cs, @"^(\*\.[^;\s]+;)*(\*\.[^;\s]+;?)$").Success) {
                                    callStrings.Add(cs);
                                }
                            }
                        }
                        else {
                            callStrings.AddRange(c.CallStrings);
                        }
                    }
                }

                var plugins = from p in Executioner.PluginService.Plugins
                              select p.Instance;
                foreach (IPlugin p in plugins) {
                    if (!p.IsEnabled) {
                        continue;
                    }

                    if (p is IExecutable) {
                        IExecutable e = p as IExecutable;
                        if (e.CallStrings != null) {
                            callStrings.AddRange(e.CallStrings);
                        }
                    }

                    if (p is IExecutableCommands) {
                        IExecutableCommands e = p as IExecutableCommands;
                        if (e.CallStrings != null) {
                            callStrings.AddRange(e.CallStrings);
                        }
                    }
                }

                callStrings.AddRange(Executioner._Instance.ExecutionerCallStrings);

                return callStrings;
            }
        }
        public static List<string> FileCallStrings {
            get {
                List<string> callStrings = new List<string>();

                var commands = from cg in Executioner.Commands.Values
                               from c in cg
                               where c is TriggerCommand
                               select c;

                foreach (IExecutable c in commands) {
                    if (c.CallStrings != null) {                        
                        foreach (string cs in c.CallStrings) {
                            if (Regex.Match(cs, @"^(\*\.[^;\s]+;)*(\*\.[^;\s]+;?)$").Success) {
                                callStrings.AddRange(cs.Split(
                                    new string[]{";", "*"}, 
                                    SplitOptions.Trim | 
                                    SplitOptions.RemoveEmptyEntries
                                ));
                            }
                        }
                    }
                }

                return callStrings.Distinct().ToList();                
            }
        }

        private void LoadCommands() {
            Executioner.Commands = new Dictionary<string, List<ICommand>>();

            string fileName = Executioner.Directory + "commands.xml";
            if (!File.Exists(fileName)) {
                return;
            }

            string document = Executioner.Data.Load(fileName);

            XDocument xmlDoc = XDocument.Parse(document);

            if (xmlDoc.Element("Executioner") == null ||
                xmlDoc.Element("Executioner").Element("Commands") == null
            ) {
                return;
            }
            
            var commands = from cmd in xmlDoc.Element("Executioner").Element("Commands").Elements()
                           select cmd;

            foreach (var command in commands) {
                string type = command.Name.LocalName;
                switch (type) {
                    case "Trigger":
                        try {
                            TriggerCommand tc = new TriggerCommand();
                            tc.Load(command);                            
                            this.AddCommand("Trigger", tc);
                        }
                        catch {
                            App.Log("Error loading trigger command.");
                            Executioner.Sounds.Error();
                        }
                        break;
                    default:
                        try {
                            PluginInstance<IPlugin> plugin = Executioner.PluginService[type, typeof(CommandPlugin)];
                            if (plugin != null && plugin.Instance.IsEnabled) {
                                CommandPlugin cp = plugin.Instance as CommandPlugin;
                                if (cp.CanInitialize) {
                                    ICommand pc = cp.InitializeCommand();
                                    pc.Load(command);
                                    this.AddCommand(type, pc);
                                }
                            }
                            else {
                                UnknownCommand uc = new UnknownCommand();
                                uc.Load(command);
                                this.AddCommand("Unknown", uc);
                            }
                        }
                        catch {
                            App.Log("Error loading '" + type + "' plugin command.");
                            Executioner.Sounds.Error();
                        }
                        break;
                }
            }
            
            if (Executioner.CommandsLoaded != null) {
                Executioner.CommandsLoaded(this, EventArgs.Empty);
            }
        }
        private void AddCommand(string type, ICommand command) {
            if (command == null) { return; }

            if (!Executioner.Commands.ContainsKey(type)) {
                Executioner.Commands.Add(type, new List<ICommand>());
            }
            Executioner.Commands[type].Add(command);
        }
        public static bool TriggerTypeInUse(Type triggerType) {
            try {
                var triggers = from tc in Executioner.Commands["Trigger"]
                               from hkt in ((TriggerCommand)tc).Triggers
                               where hkt.GetType().Equals(triggerType)
                               select tc as TriggerCommand;

                if (triggers.Count() > 0) {
                    return true;
                }
            }
            catch {
                App.Log("Failed to determine if '" + triggerType.Name + "' trigger type is in use.");
                Executioner.Sounds.Error();
            }

            return false;
        }
        #endregion
        #region Command Execution
        public static bool ExecuteCommand(string command) {
            return Executioner.ExecuteCommand(command, string.Empty, true, false);
        }
        public static bool ExecuteCommand(string command, string arguments) {
            return Executioner.ExecuteCommand(command, arguments, true, false);
        }
        public static bool ExecuteCommand(string command, string arguments, bool keepHistory) {
            return Executioner.ExecuteCommand(command, arguments, keepHistory, false);
        }
        public static bool ExecuteCommand(string command, string arguments, bool keepHistory, bool fullScreenQueue) {
            // Queue commands when in a full screen app to prevent them from stealing focus such as
            // notificatio area messages.
            if (fullScreenQueue && FullScreenDetector.IsFullScreen()) {
                Executioner._Instance._Queue.Add(new CommandInfo(command, arguments, keepHistory));
                if (!Executioner._Instance._FullScreenDetector.IsEnabled) {
                    Executioner._Instance._FullScreenDetector.State = true;
                    Executioner._Instance._FullScreenDetector.IsEnabled = true;
                }

                return true;
            }
            
            command = command.Trim();
            arguments = arguments.Trim();

            bool result = false;

            if (Executioner._ExecutionCount == 0) {
                Executioner._Instance._IsFailure = false;
                Executioner._Instance._IsError = false;
            }
            
            // The max number of self executing loops before stopping (Prevents infinite loop).
            int maxExecutionCount = Executioner.Config.GetInt("Executioner", "Max Execution Count", 5).MaxMin(2, 50);
            if (Executioner._ExecutionCount + 1 > maxExecutionCount) {                    
                App.Log("Max execution count reached.", MessageType.Warning);
                Executioner._Instance._IsFailure = true;
                result = true;
            }
            else if (command.Is("@execute") || command.StartsWith("@execute:", StringComparison.Ordinal)) {
                ++Executioner._ExecutionCount;

                try {
                    if (command.Is("@execute")) {
                        command = arguments;
                    }
                    else {
                        command = command.Substring(9).Trim();
                        if (!arguments.IsEmpty()) {
                            command += " " + arguments;
                        }
                    }

                    result = Executioner._Instance.ExecuteCallstringCommands(command);                    
                }
                catch {
                    App.Log("Error executing the command '" + command + "'.");
                    Executioner.Sounds.Error();
                }

                --Executioner._ExecutionCount;
            }
            else {
                // If the command starts with a quote, assume it's in the form: "command" arguments
                if (command.StartsWith("\"", StringComparison.Ordinal)) {
                    int pos = command.IndexOf("\"", 1);
                    if (pos > 0) {
                        string args = command.Substring(pos + 1).Trim();
                        command = command.Substring(1, pos - 1).Trim();
                        arguments = (args + " " + arguments).Trim();                        
                    }
                }

                var ainfo = new ArgumentInfo(arguments);
                bool special = false;

                // Display special argument
                if (ainfo.SpecialArguments.ContainsKey("display")) {
                    special = true;
                    if (File.Exists(command)) {
                        int time = 0;
                        if (ainfo.SpecialArguments["display"].Count > 0) {
                            int.TryParse(ainfo.SpecialArguments["display"][0], out time);
                        }

                        string data = File.ReadAllText(command);
                        if (ainfo.SpecialArguments["display"].Count > 1) {
                            Executioner.Display(data, time, ainfo.SpecialArguments["display"][1]);
                        }
                        else {
                            Executioner.Display(data, time);
                        }
                        result = true;
                    }
                }

                // Copy special argument
                if (ainfo.SpecialArguments.ContainsKey("copy")) {
                    special = true;
                    if (File.Exists(command)) {
                        string data = File.ReadAllText(command);
                        Clipboard.SetText(data);
                        result = true;
                    }
                    else {
                        Clipboard.SetText(command);
                        result = true;
                    }
                }

                // If no special arguments, or the continue special argument is set
                if (!special || ainfo.SpecialArguments.ContainsKey("continue")) {
                    // If result is already true, keep it as such even if the process fails to execute
                    if (Executioner._Instance.ExecuteProcessCommand(command, arguments)) {
                        result = true;
                    }
                }

                // Command string for history
                command = ("\"" + command + "\" " + arguments).Trim();
            }

            // Non chained command done
            if (Executioner._ExecutionCount == 0) {
                // Add history if successful result
                if (keepHistory && result) {
                    Executioner.History.Add(command);
                }

                if (Executioner._Instance._IsError == true) {
                    Executioner.Sounds.Error();
                }
                else if (Executioner._Instance._IsFailure == true) {
                    Executioner.Sounds.ErrorNotify();
                }
            }

            // If exit command was executed then exit
            if (Executioner._Instance._IsExit) {
                Executioner._Instance.Exit();
            }

            return result;
        }

        private bool ExecuteCallstringCommands(string command) {
            // Skip over quoted items when splitting multiple commands
            SkipString skip = new SkipString();
            SkipItem skipItem = new SkipItem("\"", "\"");
            skipItem.AddIgnoreString("\\\"");
            skip.AddSkipItem(skipItem);
            
            string[] commands = command.Split(
                ";", 
                SplitOptions.Trim | 
                SplitOptions.RemoveEmptyEntries
            );
            
            return this.ExecuteCallstringCommands(commands);
        }
        private bool ExecuteCallstringCommands(IEnumerable commands) {           
            bool success = true;
                
            foreach (string c in commands) {
                if (!this.ExecuteCallstringCommand(c)) {
                    success = false;
                }
            }

            return success;
        }
        private bool ExecuteCallstringCommand(string command) {
            command = command.Trim();

            string callString = string.Empty;
            string parameters = string.Empty;
            bool found = false;
            List<string> callStrings;

            // If command is a file, check for file extension triggered commands
            if (File.Exists(command)) {
                string ext = Path.GetExtension(command);
                callStrings = Executioner.FileCallStrings;
                if (callStrings.Contains(ext)) {
                    if (this.ExecuteFileCallableCommand(ext, command) ||
                        this.ExecuteProcessCommand(command)
                    ) {
                        return true;
                    }

                    return false;
                }
            }

            // Get all command names and reverse order for greedy 
            // command name in case name has spaces
            callStrings = Executioner.CallStrings;
            callStrings.OrderBy(p => p).Reverse();

            foreach (string cs in callStrings) {
                if (command == cs || command.StartsWith(cs + " ", StringComparison.Ordinal)) {
                    callString = command.Substring(0, cs.Length);
                    parameters = command.Substring(cs.Length).Trim();
                    found = true;
                    break;
                }
            }

            if (found) {
                if (this.ExecuteExecutionerCommand(callString, parameters) ||
                    this.ExecuteCallableCommand(callString, parameters)
                ) {
                    return true;
                }

                try {
                    // If the plugin is an executable type then execute it!
                    var plugins = from p in Executioner.PluginService.Plugins
                                  select p.Instance;
                    foreach (IPlugin p in plugins) {
                        if (!p.IsEnabled) {
                            continue;
                        }

                        if (p is IExecutable) {
                            IExecutable e = p as IExecutable;
                            if (e.CallStrings.Contains(callString)) {
                                switch (e.Execute(parameters)) {
                                    case ExecutionResult.Failure:
                                        this._IsFailure = true;
                                        return true;
                                    case ExecutionResult.Error:
                                        this._IsError = true;
                                        return true;
                                    case ExecutionResult.Success:
                                        return true;
                                }
                            }
                        }

                        if (p is IExecutableCommands) {
                            IExecutableCommands e = p as IExecutableCommands;
                            if (e.CallStrings.Contains(callString)) {
                                switch (e.ExecuteCommand(callString, parameters)) {
                                    case ExecutionResult.Failure:
                                        this._IsFailure = true;
                                        return true;
                                    case ExecutionResult.Error:
                                        this._IsError = true;
                                        return true;
                                    case ExecutionResult.Success:
                                        return true;
                                }
                            }
                        }
                    }
                }
                catch {
                    App.Log("Error executing plugin command '" + command + "'.");
                    Executioner.Sounds.Error();
                    return true;
                }
            }
            else {
                // Open url in browser
                if (this.ExecuteUrlCommand(command)) {
                    return true;
                }

                // Try to run it as a process
                if (this.ExecuteProcessCommand(command)) {
                    return true;
                }
            }

            return false;
        }

        private bool ExecuteExecutionerCommand(string callString, string parameters) {
            switch (callString) {
                case "-exit":                    
                    // Tell executioner to exit once done processing this command
                    this._IsExit = true;
                    return true;
                case "-reload": // Reload commangs and plugins
                    this.Reload();
                    return true;
                case "-cmds": // Open commands.xml
                    this.ExecuteProcessCommand(Executioner.Directory + "\\commands.xml");
                    return true;
                case "-config":
                    this.ShowConfig();
                    return true;
                case "-exec":
                    this.ExecuteProcessCommand(Executioner.Directory);
                    return true;
                case "-clear":
                    // Clear history
                    Executioner.History.Clear(parameters);

                    // Check plugins for IClearable
                    var plugins = from p in Executioner.PluginService.Plugins
                                  select p.Instance;

                    foreach (IPlugin p in plugins) {
                        if (p.IsEnabled && p is IClearable) {
                            IClearable c = p as IClearable;
                            c.Clear(parameters);
                        }
                    }

                    return true;
                case "-scripts":
                    this.ExecuteProcessCommand(Executioner.Directory + "\\scripts");
                    return true;
                case "-website":
                    this.ExecuteProcessCommand("http://hetymine.com/executioner/");
                    return true;
                case "-display":
                    var prams = new Hetymine.Executioner.Data.ParameterInfo(parameters);
                    if (prams.IsEmpty) {
                        this._IsFailure = true;
                        return true;
                    }
                    
                    bool hasT = (prams.Flags.ContainsKey("t") && prams.Flags["t"].Parameters.Count != 0);
                    bool hasP = (prams.Flags.ContainsKey("p") && prams.Flags["p"].Parameters.Count != 0);

                    int time = 0;
                    if (hasT) {
                        int.TryParse(prams.Flags["t"].Parameters[0], out time);
                    }
                    else if (prams.Count > 1) {
                        int.TryParse(prams.Parameters[1], out time);
                    }

                    string display = Executioner.Config.GetString("Executioner", "Display Plugin", string.Empty);
                    if (hasP) {
                        // Use first found plugin that exists
                        foreach (string s in prams.Flags["p"].Parameters) {
                            var plugin = Executioner.PluginService[display, typeof(IDisplay)];
                            if (plugin != null) {
                                display = s;
                                break;
                            }
                        }
                    }
                    else if (prams.Count > 2) {
                        display = prams.Parameters[2];
                    }

                    if (hasT && hasP) {
                        Executioner.Display(prams.Join(), time, display);
                    }
                    else {
                        Executioner.Display(prams.Parameters[0], time, display);
                    }

                    return true;
            }

            return false;
        }

        private bool ExecuteCallableCommand(string callString) {
            return this.ExecuteCallableCommand(callString, string.Empty);
        }
        private bool ExecuteCallableCommand(string callString, string parameters) {
            bool success = false; // Has a command been executed

            if (Executioner.Commands.ContainsKey("Trigger")) {
                var triggers = from tc in Executioner.Commands["Trigger"]
                               where tc.CallStrings.Contains(callString)
                               select tc;

                // If no parameters and call string matches a trigger command
                if (triggers.Count() > 0) {                    
                    foreach (TriggerCommand tc in triggers) {
                        switch (tc.Execute(parameters)) {
                            case ExecutionResult.Failure:
                                this._IsFailure = true;
                                success = true;
                                break;
                            case ExecutionResult.Error:
                                this._IsError = true;
                                success = true;
                                break;
                            case ExecutionResult.Success:
                                success = true;
                                break;                            
                        }
                    }

                    if (success) {
                        return true;
                    }
                }
            }

            // Otherwise execute all other commands            
            var commands = from cg in Executioner.Commands.Keys
                           from c in Executioner.Commands[cg]
                           where cg != "Trigger" && c.CallStrings.Contains(callString)
                           select c;

            if (commands.Count() > 0) {                
                foreach (var c in commands) {
                    switch (c.Execute(parameters)) {
                        case ExecutionResult.Failure:
                            this._IsFailure = true;
                            success = true;
                            break;
                        case ExecutionResult.Error:
                            this._IsError = true;
                            success = true;
                            break;
                        case ExecutionResult.Success:
                            success = true;
                            break;
                    }
                }
            }

            return success;
        }

        private bool ExecuteFileCallableCommand(string extension, string fileName) {
            bool success = false;

            if (Executioner.Commands.ContainsKey("Trigger")) {
                var triggers = from tc in Executioner.Commands["Trigger"]
                               from cs in tc.CallStrings
                               where cs.IndexOf("*" + extension + ";", StringComparison.Ordinal) != -1 ||
                                     cs.EndsWith("*" + extension, StringComparison.Ordinal)
                               select tc;

                if (triggers.Count() > 0) {                    
                    foreach (TriggerCommand tc in triggers) {
                        switch (tc.Execute(fileName)) {
                            case ExecutionResult.Failure:
                                this._IsFailure = true;
                                success = true;
                                break;
                            case ExecutionResult.Error:
                                this._IsError = true;
                                success = true;
                                break;
                            case ExecutionResult.Success:
                                success = true;
                                break;
                        }
                    }
                }
            }

            return success;
        }

        private bool ExecuteUrlCommand(string command) {
            int index = command.IndexOf("://", StringComparison.Ordinal); // Any protocal
            if (index != -1) {
                if (index < 1) { return false; } // Shouldn't start as ://

                return this.ExecuteProcessCommand(command);
            }

            // Local web server
            if (command.StartsWith("localhost", StringComparison.Ordinal)) {
                return this.ExecuteProcessCommand("http://" + command);
            }

            // www is most likely a url
            if (command.StartsWith("www.", StringComparison.Ordinal)) {
                return this.ExecuteProcessCommand("http://" + command);
            }

            // Assume commands ending in .exe are not urls and rather the user
            // relying on the default command to for example search for information
            // on that exe file
            if (command.EndsWith(".exe", StringComparison.Ordinal)) {
                return false;
            }
            if (command.EndsWith(".dll", StringComparison.Ordinal)) {
                return false;
            }

            // If a space is found, assume not a url
            index = command.IndexOf(" ", StringComparison.Ordinal);
            if (index > 0) {
                return false;
            }

            // Urls should have at least 1 period in it
            index = command.IndexOf(".", StringComparison.Ordinal);
            if (index > 1) {
                // Add protocal if non existant
                if (command.IndexOf("://", StringComparison.Ordinal) > 1) {
                    return this.ExecuteProcessCommand(command);
                }
                else {
                    return this.ExecuteProcessCommand("http://" + command);
                }
            }

            return false;
        }

        private bool ExecuteProcessCommand(string command) {
            return this.ExecuteProcessCommand(command, string.Empty);
        }
        private bool ExecuteProcessCommand(string command, string arguments) {
            try {
                Process.Start(command, arguments);
            }
            catch {
                return false;
            }

            return true;
        }
        #endregion
        #region Properties
        public static HistoryManager History { get; set; }
        public static string Directory {
            get {
                return AppDomain.CurrentDomain.BaseDirectory;
            }
        }
        public static string PluginDirectory {
            get {
                return Executioner.Directory + "plugins" + Path.DirectorySeparatorChar;
            }
        }
        public static string ScriptDirectory {
            get {
                return Executioner.Directory + "scripts" + Path.DirectorySeparatorChar;
            }
        }
        public static string CacheDirectory {
            get {
                return Executioner.Directory + "cache" + Path.DirectorySeparatorChar;
            }
        }
        public static Config ConfigWindow {
            get { return Executioner._Instance._ConfigWindow; } 
        }
        public static string Version { get; protected set; }
        public static string VersionString { get; protected set; }
        #endregion
        #region Display
        [DllImport("user32.dll", EntryPoint = "FindWindowEx")]
        private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
        [DllImport("user32.dll")]
        private static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);

        public static void Display(string data) {
            Executioner.Display(data, 0);
        }
        public static void Display(string data, int time) {
            string display = Executioner.Config.GetString("Executioner", "Display Plugin", string.Empty);
            Executioner.Display(data, time, display);
        }
        public static void Display(string data, int time, string display) {
            var plugin = Executioner.PluginService[display, typeof(IDisplay)];
            if (plugin != null) {
                ((IDisplay)plugin.Instance).Display(data, time);
            }
            else {
                ProcessStartInfo startInfo = new ProcessStartInfo("notepad");                
                Process notepad = Process.Start(startInfo);                
                if (notepad == null) {
                    App.Log("Notepad not found.");
                    Executioner.Sounds.Error();
                    MessageBox.Show(data, "Executioner", MessageBoxButton.OK);
                }
                else {
                    notepad.WaitForInputIdle();
                    try {
                        IntPtr child = Executioner.FindWindowEx(notepad.MainWindowHandle, new IntPtr(0), "Edit", null);
                        SendMessage(child, 0x000C, 0, data);
                    }
                    catch {
                        App.Log("Error displaying data in notepad.");
                        Executioner.Sounds.Error();
                        MessageBox.Show(data, "Executioner", MessageBoxButton.OK);
                    }
                }
            }
        }
        #endregion
        #region Config
        public static IniFile Config { get; protected set; }
        private void ShowConfig() {
            if (this._ConfigWindow != null) {
                this._ConfigWindow.Activate();
                return;
            }            
            this._ConfigWindow = new Config(this.CloneCommands());            
            this._ConfigWindow.Closed += new EventHandler(Config_Closed);
            this._ConfigWindow.Show();

            this._ConfigWindow.Left = Executioner.Config.GetDouble("Executioner", "Config Window Left", double.NaN);
            this._ConfigWindow.Top = Executioner.Config.GetDouble("Executioner", "Config Window Top", double.NaN);
            this._ConfigWindow.EnsureOnScreen();

            this._ConfigWindow.Activate();
        }
        private void Config_Closed(object sender, EventArgs e) {
            // We update executioners config to saved one since plugins can
            // save config during unload proccess
            if (this._ConfigWindow.Saved) {
                Executioner.Config = this._ConfigWindow.Data;
            }

            Executioner.Config.SetDouble("Executioner", "Config Window Left", this._ConfigWindow.Left);
            Executioner.Config.SetDouble("Executioner", "Config Window Top", this._ConfigWindow.Top);

            this._ConfigWindow = null;
            this.Reload();
        }
        #endregion                
        #region Window Hook Handler
        private IntPtr HookHandler(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
            handled = false;

            if (!this._IsLoaded) {
                return IntPtr.Zero;
            }

            
            var plugins = from p in Executioner.PluginService.Plugins
                            where p.Instance is IWindowHook
                            select p.Instance;
            foreach (IWindowHook p in plugins) {
                // Later in config have executioner order and stops when handler returns true
                // to resolve conflicts in plugin message handling
                bool stop = p.HookHandler(hwnd, msg, wParam, lParam, ref handled);
                if (handled || stop) {
                    return IntPtr.Zero;
                }
            }

            return IntPtr.Zero;            
        }
        public static IntPtr Handle {
            get { return Executioner._Handle.Handle; }
        }
        #endregion
    }
}
